using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Volo.Abp.Domain.Repositories.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore;
using Matrix.Notification.Domain.Notifications;
using Matrix.Notification.Domain.Shared.Notifications;

namespace Matrix.Notification.EntityFrameworkCore.Notifications
{
    /// <summary>
    /// 通知批次仓储实现
    /// </summary>
    public class NotificationBatchRepository : EfCoreRepository<NotificationDbContext, NotificationBatch, Guid>, INotificationBatchRepository
    {
        public NotificationBatchRepository(IDbContextProvider<NotificationDbContext> dbContextProvider)
            : base(dbContextProvider)
        {
        }

        public async Task<NotificationBatch?> GetWithDetailsAsync(
            Guid id,
            CancellationToken cancellationToken = default)
        {
            return await DbSet
                .IncludeDetails()
                .FirstOrDefaultAsync(x => x.Id == id, GetCancellationToken(cancellationToken));
        }

        public async Task<List<NotificationBatch>> GetListAsync(
            string? sorting = null,
            int maxResultCount = int.MaxValue,
            int skipCount = 0,
            string? filter = null,
            NotificationBatchType? type = null,
            NotificationType? notificationType = null,
            NotificationBatchStatus? status = null,
            Guid? senderId = null,
            Guid? templateId = null,
            DateTime? startTime = null,
            DateTime? endTime = null,
            string? category = null,
            CancellationToken cancellationToken = default)
        {
            return await (await GetQueryableAsync())
                .IncludeDetails()
                .WhereIf(!filter.IsNullOrWhiteSpace(), x =>
                    x.Name.Contains(filter!) ||
                    x.Description!.Contains(filter!) ||
                    x.BatchCode.Contains(filter!))
                .WhereIf(type.HasValue, x => x.Type == type.Value)
                .WhereIf(notificationType.HasValue, x => x.NotificationType == notificationType.Value)
                .WhereIf(status.HasValue, x => x.Status == status.Value)
                .WhereIf(senderId.HasValue, x => x.SenderId == senderId.Value)
                .WhereIf(templateId.HasValue, x => x.TemplateId == templateId.Value)
                .WhereIf(startTime.HasValue, x => x.CreationTime >= startTime.Value)
                .WhereIf(endTime.HasValue, x => x.CreationTime <= endTime.Value)
                .WhereIf(!category.IsNullOrWhiteSpace(), x => x.Tags.Contains(category))
                .OrderBy(sorting ?? nameof(NotificationBatch.CreationTime) + " DESC")
                .PageBy(skipCount, maxResultCount)
                .ToListAsync(GetCancellationToken(cancellationToken));
        }

        public async Task<long> GetCountAsync(
            string? filter = null,
            NotificationBatchType? type = null,
            NotificationType? notificationType = null,
            NotificationBatchStatus? status = null,
            Guid? senderId = null,
            Guid? templateId = null,
            DateTime? startTime = null,
            DateTime? endTime = null,
            string? category = null,
            CancellationToken cancellationToken = default)
        {
            return await (await GetQueryableAsync())
                .WhereIf(!filter.IsNullOrWhiteSpace(), x =>
                    x.Name.Contains(filter!) ||
                    x.Description!.Contains(filter!) ||
                    x.BatchCode.Contains(filter!))
                .WhereIf(type.HasValue, x => x.Type == type.Value)
                .WhereIf(notificationType.HasValue, x => x.NotificationType == notificationType.Value)
                .WhereIf(status.HasValue, x => x.Status == status.Value)
                .WhereIf(senderId.HasValue, x => x.SenderId == senderId.Value)
                .WhereIf(templateId.HasValue, x => x.TemplateId == templateId.Value)
                .WhereIf(startTime.HasValue, x => x.CreationTime >= startTime.Value)
                .WhereIf(endTime.HasValue, x => x.CreationTime <= endTime.Value)
                .WhereIf(!category.IsNullOrWhiteSpace(), x => x.Tags.Contains(category))
                .LongCountAsync(GetCancellationToken(cancellationToken));
        }

        public async Task<List<NotificationBatch>> GetScheduledBatchesAsync(
            CancellationToken cancellationToken = default)
        {
            return await (await GetQueryableAsync())
                .IncludeDetails()
                .Where(x => x.Status == NotificationBatchStatus.Scheduled)
                .Where(x => x.ScheduledTime.HasValue && x.ScheduledTime <= DateTime.UtcNow)
                .OrderBy(x => x.ScheduledTime)
                .ThenBy(x => x.Priority)
                .ToListAsync(GetCancellationToken(cancellationToken));
        }

        public async Task<List<NotificationBatch>> GetActiveBatchesAsync(
            CancellationToken cancellationToken = default)
        {
            return await (await GetQueryableAsync())
                .IncludeDetails()
                .Where(x => x.Status == NotificationBatchStatus.Sending || x.Status == NotificationBatchStatus.Paused)
                .OrderBy(x => x.StartedTime)
                .ToListAsync(GetCancellationToken(cancellationToken));
        }

        public async Task<List<NotificationBatch>> GetReadyBatchesAsync(
            CancellationToken cancellationToken = default)
        {
            return await (await GetQueryableAsync())
                .IncludeDetails()
                .Where(x => x.Status == NotificationBatchStatus.Ready ||
                           (x.Status == NotificationBatchStatus.Scheduled &&
                            x.ScheduledTime.HasValue &&
                            x.ScheduledTime <= DateTime.UtcNow))
                .OrderByDescending(x => x.Priority)
                .ThenBy(x => x.CreationTime)
                .ToListAsync(GetCancellationToken(cancellationToken));
        }

        public async Task<List<NotificationBatch>> GetFailedBatchesAsync(
            DateTime? since = null,
            CancellationToken cancellationToken = default)
        {
            return await (await GetQueryableAsync())
                .IncludeDetails()
                .Where(x => x.Status == NotificationBatchStatus.Failed)
                .WhereIf(since.HasValue, x => x.CreationTime >= since.Value)
                .OrderByDescending(x => x.CreationTime)
                .ToListAsync(GetCancellationToken(cancellationToken));
        }

        public async Task<List<NotificationBatch>> GetBatchesBySenderAsync(
            Guid senderId,
            int maxCount = 50,
            CancellationToken cancellationToken = default)
        {
            return await (await GetQueryableAsync())
                .IncludeDetails()
                .Where(x => x.SenderId == senderId)
                .OrderByDescending(x => x.CreationTime)
                .Take(maxCount)
                .ToListAsync(GetCancellationToken(cancellationToken));
        }

        public async Task<List<NotificationBatch>> GetBatchesByTemplateAsync(
            Guid templateId,
            int maxCount = 50,
            CancellationToken cancellationToken = default)
        {
            return await (await GetQueryableAsync())
                .IncludeDetails()
                .Where(x => x.TemplateId == templateId)
                .OrderByDescending(x => x.CreationTime)
                .Take(maxCount)
                .ToListAsync(GetCancellationToken(cancellationToken));
        }

        public async Task<NotificationBatch?> GetByBatchCodeAsync(
            string batchCode,
            CancellationToken cancellationToken = default)
        {
            return await (await GetQueryableAsync())
                .IncludeDetails()
                .FirstOrDefaultAsync(x => x.BatchCode == batchCode, GetCancellationToken(cancellationToken));
        }

        public async Task<NotificationBatch?> GetByExternalBatchIdAsync(
            string externalBatchId,
            CancellationToken cancellationToken = default)
        {
            return await (await GetQueryableAsync())
                .IncludeDetails()
                .FirstOrDefaultAsync(x => x.ExternalBatchId == externalBatchId, GetCancellationToken(cancellationToken));
        }

        public async Task<List<NotificationBatch>> GetBatchesByRelatedEntityAsync(
            Guid entityId,
            string entityType,
            CancellationToken cancellationToken = default)
        {
            return await (await GetQueryableAsync())
                .IncludeDetails()
                .Where(x => x.RelatedEntityId == entityId && x.RelatedEntityType == entityType)
                .OrderByDescending(x => x.CreationTime)
                .ToListAsync(GetCancellationToken(cancellationToken));
        }

        public async Task<List<NotificationBatch>> GetPausedBatchesAsync(
            CancellationToken cancellationToken = default)
        {
            return await (await GetQueryableAsync())
                .IncludeDetails()
                .Where(x => x.Status == NotificationBatchStatus.Paused)
                .OrderBy(x => x.PausedTime)
                .ToListAsync(GetCancellationToken(cancellationToken));
        }

        public async Task<List<NotificationBatch>> GetLongRunningBatchesAsync(
            TimeSpan threshold,
            CancellationToken cancellationToken = default)
        {
            var cutoffTime = DateTime.UtcNow.Subtract(threshold);
            return await (await GetQueryableAsync())
                .IncludeDetails()
                .Where(x => x.Status == NotificationBatchStatus.Sending &&
                           x.StartedTime.HasValue &&
                           x.StartedTime < cutoffTime)
                .OrderBy(x => x.StartedTime)
                .ToListAsync(GetCancellationToken(cancellationToken));
        }

        public async Task<List<NotificationBatch>> GetBatchesForCleanupAsync(
            DateTime beforeDate,
            CancellationToken cancellationToken = default)
        {
            return await (await GetQueryableAsync())
                .IncludeDetails()
                .Where(x => (x.Status == NotificationBatchStatus.Completed ||
                           x.Status == NotificationBatchStatus.Cancelled ||
                           x.Status == NotificationBatchStatus.Failed) &&
                          x.CompletedTime.HasValue &&
                          x.CompletedTime < beforeDate)
                .OrderBy(x => x.CompletedTime)
                .ToListAsync(GetCancellationToken(cancellationToken));
        }

        public async Task<NotificationBatchStatisticsDto> GetBatchStatisticsAsync(
            Guid? senderId = null,
            DateTime? startTime = null,
            DateTime? endTime = null,
            CancellationToken cancellationToken = default)
        {
            var queryable = await GetQueryableAsync();

            queryable = queryable
                .WhereIf(senderId.HasValue, x => x.SenderId == senderId)
                .WhereIf(startTime.HasValue, x => x.CreationTime >= startTime.Value)
                .WhereIf(endTime.HasValue, x => x.CreationTime <= endTime.Value);

            var statistics = new NotificationBatchStatisticsDto
            {
                TotalCount = await queryable.LongCountAsync(GetCancellationToken(cancellationToken)),
                DraftCount = await queryable.CountAsync(x => x.Status == NotificationBatchStatus.Draft, GetCancellationToken(cancellationToken)),
                ReadyCount = await queryable.CountAsync(x => x.Status == NotificationBatchStatus.Ready, GetCancellationToken(cancellationToken)),
                SendingCount = await queryable.CountAsync(x => x.Status == NotificationBatchStatus.Sending, GetCancellationToken(cancellationToken)),
                CompletedCount = await queryable.CountAsync(x => x.Status == NotificationBatchStatus.Completed, GetCancellationToken(cancellationToken)),
                FailedCount = await queryable.CountAsync(x => x.Status == NotificationBatchStatus.Failed, GetCancellationToken(cancellationToken)),
                PausedCount = await queryable.CountAsync(x => x.Status == NotificationBatchStatus.Paused, GetCancellationToken(cancellationToken)),
                CancelledCount = await queryable.CountAsync(x => x.Status == NotificationBatchStatus.Cancelled, GetCancellationToken(cancellationToken))
            };

            // 计算总统计数据
            var totalBatches = await queryable.ToListAsync(GetCancellationToken(cancellationToken));
            if (totalBatches.Any())
            {
                statistics.TotalRecipients = totalBatches.Sum(x => x.TotalRecipients);
                statistics.SentCount = totalBatches.Sum(x => x.SentCount);
                statistics.DeliveredCount = totalBatches.Sum(x => x.DeliveredCount);
                statistics.FailedCount = totalBatches.Sum(x => x.FailedCount);
                statistics.ReadCount = totalBatches.Sum(x => x.ReadCount);
                statistics.ConfirmedCount = totalBatches.Sum(x => x.ConfirmedCount);

                statistics.AverageSuccessRate = totalBatches
                    .Where(x => x.SentCount > 0)
                    .Average(x => x.CalculateSuccessRate());

                statistics.AverageReadRate = totalBatches
                    .Where(x => x.DeliveredCount > 0)
                    .Average(x => x.CalculateReadRate());
            }

            return statistics;
        }

        public async Task UpdateBatchProgressAsync(
            Guid batchId,
            NotificationBatchStatus status,
            int sentCount = 0,
            int deliveredCount = 0,
            int failedCount = 0,
            int readCount = 0,
            int confirmedCount = 0,
            string? failureReason = null,
            CancellationToken cancellationToken = default)
        {
            var dbContext = await GetDbContextAsync();
            var batch = await DbSet.FindAsync(new object[] { batchId }, GetCancellationToken(cancellationToken));

            if (batch != null)
            {
                switch (status)
                {
                    case NotificationBatchStatus.Sending:
                        batch.StartSending();
                        break;
                    case NotificationBatchStatus.Completed:
                        batch.Complete();
                        break;
                    case NotificationBatchStatus.Paused:
                        batch.Pause(failureReason);
                        break;
                    case NotificationBatchStatus.Cancelled:
                        batch.Cancel(failureReason);
                        break;
                    case NotificationBatchStatus.Failed:
                        batch.MarkAsFailed(failedCount, failureReason);
                        break;
                }

                // 更新统计信息
                if (sentCount > 0) batch.MarkAsSent(sentCount);
                if (deliveredCount > 0) batch.MarkAsDelivered(deliveredCount);
                if (readCount > 0) batch.MarkAsRead(readCount);
                if (confirmedCount > 0) batch.MarkAsConfirmed(confirmedCount);

                await dbContext.SaveChangesAsync(GetCancellationToken(cancellationToken));
            }
        }

        public async Task<bool> IsNameExistsAsync(
            string name,
            Guid? excludeId = null,
            CancellationToken cancellationToken = default)
        {
            return await (await GetQueryableAsync())
                .WhereIf(excludeId.HasValue, x => x.Id != excludeId.Value)
                .AnyAsync(x => x.Name == name, GetCancellationToken(cancellationToken));
        }

        public async Task<bool> IsBatchCodeExistsAsync(
            string batchCode,
            Guid? excludeId = null,
            CancellationToken cancellationToken = default)
        {
            return await (await GetQueryableAsync())
                .WhereIf(excludeId.HasValue, x => x.Id != excludeId.Value)
                .AnyAsync(x => x.BatchCode == batchCode, GetCancellationToken(cancellationToken));
        }
    }

    /// <summary>
    /// 通知批次统计DTO
    /// </summary>
    public class NotificationBatchStatisticsDto
    {
        public long TotalCount { get; set; }
        public int DraftCount { get; set; }
        public int ReadyCount { get; set; }
        public int SendingCount { get; set; }
        public int CompletedCount { get; set; }
        public int FailedCount { get; set; }
        public int PausedCount { get; set; }
        public int CancelledCount { get; set; }

        public int TotalRecipients { get; set; }
        public int SentCount { get; set; }
        public int DeliveredCount { get; set; }
        public int ReadCount { get; set; }
        public int ConfirmedCount { get; set; }

        public double AverageSuccessRate { get; set; }
        public double AverageReadRate { get; set; }
        public double AverageCompletionRate { get; set; }

        public DateTime StatisticsTime { get; set; } = DateTime.UtcNow;
    }
}